# Функции высшего порядка

Функция высшего порядка (HOF - higher-order function) часто определяется как функция, которая 

- принимает другие функции в качестве входных параметров или 
- возвращает функцию в качестве результата. 

В Scala HOF возможны, потому что функции являются [объектами первого класса](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8_%D0%BF%D0%B5%D1%80%D0%B2%D0%BE%D0%B3%D0%BE_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%B0).

> В качестве важного примечания: 
> хотя в этом документе используется общепринятый термин "функция высшего порядка", 
> в Scala эта фраза применима как к методам, так и к функциям. 
> Благодаря технологии [Eta Expansion](eta.md) их, как правило, можно использовать в одних и тех же местах.

### От потребителя к разработчику

В примерах, приведенных ранее в документации, было видно, как пользоваться методами, 
которые принимают другие функции в качестве входных параметров, например, `map` и `filter`. 

В следующих разделах будет показано, как создавать HOF, в том числе:

- как писать методы, принимающие функции в качестве входных параметров
- как возвращать функции из методов

В процессе будет видно:

- синтаксис, который используется для определения входных параметров функции
- как вызвать функцию, если есть на нее ссылка

В качестве полезного побочного эффекта, как только синтаксис станет привычным, 
его можно начать использовать для определения параметров функций, анонимных функций и функциональных переменных, 
а также станет легче читать Scaladoc для функций высшего порядка.

### Понимание Scaladoc метода filter

Чтобы понять, как работают функции высшего порядка, рассмотрим пример: 
определим, какой тип функций принимает `filter`, взглянув на 
[его Scaladoc](https://scala-lang.org/api/3.x/scala/collection/immutable/List.html#filter-5a1). 
Вот определение `filter` в классе `List[A]`:

```dotty
def filter(p: (A) => Boolean): List[A]
```

Это определение указывает на то, что `filter` - метод, который принимает параметр функции с именем `p`. 
По соглашению, `p` обозначает _предикат_, который представляет собой просто функцию, возвращающую `Boolean`. 
Таким образом, `filter` принимает предикат `p` в качестве входного параметра и возвращает `List[A]`, 
где `A` - тип, содержащийся в списке; если `filter` вызывается для `List[Int]`, то `A` - это тип `Int`.

На данный момент, если не учитывать назначение метода `filter`, все, что известно, 
так это то, что алгоритм каким-то образом использует предикат `p` для создания и возврата `List[A]`.

Если посмотреть конкретно на параметр функции `p`: `p: (A) => Boolean`, то эта часть описания `filter` означает, 
что любая передаваемая функция должна принимать тип `A` в качестве входного параметра и возвращать `Boolean`. 
Итак, если список представляет собой список `List[Int]`, 
то можно заменить универсальный тип `A` на `Int` и прочитать эту подпись следующим образом: `p: (Int) => Boolean`.

Поскольку `isEven` имеет такой же тип — преобразует входное значение `Int` в результирующее `Boolean` — 
его можно использовать с `filter`.

### Написание методов, которые принимают параметры функции

Рассмотрим пример написания методов, которые принимают функции в качестве входных параметров.

> Для определенности, будем называть код, который пишется, _методом_, 
> а код, принимаемый в качестве входного параметра, — _функцией_.

#### Пример

Чтобы создать метод, который принимает функцию в качестве параметра, необходимо:

- в списке параметров метода определить сигнатуру принимаемой функции
- использовать эту функцию внутри метода

Чтобы продемонстрировать это, вот метод, который принимает входной параметр с именем `f`, где `f` — функция:

```dotty
def sayHello(f: () => Unit): Unit = f()
```

Эта часть кода — сигнатура типа (_type signature_) — утверждает, что `f` является функцией, 
и определяет типы функций, которые будет принимать метод `sayHello`: `f: () => Unit`.

Как это работает:

- `f` — имя входного параметра функции. 
Аналогично тому, как параметр `String` обычно называется `s` или параметр `Int` - `i`
- сигнатура типа `f` определяет тип функций, которые будет принимать метод
- часть `()` подписи `f` (слева от символа `=>`) указывает на то, что `f` не принимает входных параметров
- часть сигнатуры `Unit` (справа от символа `=>`) указывает на то, что функция `f` не должна возвращать осмысленный результат
- в теле метода `sayHello` (справа от символа `=`) оператор `f()` вызывает переданную функцию

Теперь, когда `sayHello` определен, создадим функцию, соответствующую сигнатуре `f`, чтобы ее можно было проверить. 
Следующая функция не принимает входных параметров и ничего не возвращает, поэтому она соответствует сигнатуре типа `f`:

```dotty
def helloJoe(): Unit = println("Hello, Joe")
```

Поскольку сигнатуры типов совпадают, можно передать `helloJoe` в `sayHello`:

```dotty
sayHello(helloJoe)
// Hello, Joe
```

Был определен метод с именем `sayHello`, который принимает функцию в качестве входного параметра, 
а затем вызывает эту функцию в теле своего метода.

##### sayHello может принимать разные функции

Важно знать, что преимущество этого подхода заключается не в том, 
что `sayHello` может принимать одну функцию в качестве входного параметра;
преимущество в том, что `sayHello` может принимать любую функцию, соответствующую сигнатуре `f`. 
Например, поскольку следующая функция не принимает входных параметров и ничего не возвращает, 
она также работает с `sayHello`:

```dotty
def bonjourJulien(): Unit = println("Bonjour, Julien")
```

```dotty
sayHello(bonjourJulien)
// Bonjour, Julien
```

Рассмотрим ещё несколько примеров того, как определять сигнатуры различных типов для параметров функции.

### Общий синтаксис для определения входных параметров функции

В методе:

```dotty
def sayHello(f: () => Unit): Unit
```

сигнатурой типа для `f` является `() => Unit`.

Это сигнатура означает "функцию, которая не принимает входных параметров и не возвращает ничего значимого (`Unit`)".

Вот сигнатура функции, которая принимает параметр `String` и возвращает `Int`:

```dotty
f: (String) => Int
```

Какие функции принимают строку и возвращают целое число? 
Например, такие, как "длина строки" и контрольная сумма.

Эта функция принимает два параметра `Int` и возвращает `Int`:

```dotty
f: (Int, Int) => Int
```

Какие функции соответствуют данной сигнатуре?

Любая функция, которая принимает два входных параметра `Int` и возвращает `Int`, соответствует этой сигнатуре, 
поэтому все "функции" ниже (точнее, методы) подходят:

```dotty
def add(a: Int, b: Int): Int = a + b
def subtract(a: Int, b: Int): Int = a - b
def multiply(a: Int, b: Int): Int = a * b
```

Из примеров выше можно сделать вывод, что общий синтаксис сигнатуры функций такой:

```dotty
variableName: (parameterTypes ...) => returnType
```

> Поскольку функциональное программирование похоже на создание и объединение ряда алгебраических уравнений, 
> обычно много думают о типах при разработке функций и приложений. Можно сказать, что «думают типами».

### Параметр функции вместе с другими параметрами

Чтобы HOFs стали действительно полезными, им также нужны некоторые данные для работы. 
Для класса, подобного `List`, в его методе `map` уже есть данные для работы: элементы в `List`. 
Но для автономного приложения, у которого нет собственных данных, 
метод также должен принимать в качестве других входных параметров данные.

Рассмотрим пример метода с именем `executeNTimes`, который имеет два входных параметра: функцию и `Int`:

```dotty
def executeNTimes(f: () => Unit, n: Int): Unit =
  for i <- 1 to n do f()
```

Как видно из кода, `executeNTimes` выполняет функцию `f` `n` раз. 
Поскольку простой цикл `for`, подобный этому, не имеет возвращаемого значения, `executeNTimes` возвращает `Unit`.

Чтобы протестировать `executeNTimes`, определим метод, соответствующий сигнатуре `f`:

```dotty
def helloWorld(): Unit = println("Hello, world")
```

Затем передадим этот метод в `executeNTimes` вместе с `Int`:

```dotty
executeNTimes(helloWorld, 3)
// Hello, world
// Hello, world
// Hello, world
```

Метод `executeNTimes` трижды выполняет функцию `helloWorld`.

##### Столько параметров, сколько необходимо

Методы могут усложняться по мере необходимости. 
Например, этот метод принимает функцию типа `(Int, Int) => Int` вместе с двумя входными параметрами:

```dotty
def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit =
  println(f(i, j))
```

Поскольку методы `sum` и `multiply` соответствуют сигнатуре `f`, 
их можно передать в `executeAndPrint` вместе с двумя значениями `Int`:

```dotty
def sum(x: Int, y: Int) = x + y
def multiply(x: Int, y: Int) = x * y
executeAndPrint(sum, 3, 11)     
// 14     
executeAndPrint(multiply, 3, 9)
// 27
```

### Согласованность подписи типа функции

Самое замечательное в изучении сигнатур типов функций Scala заключается в том, 
что синтаксис, используемый для определения входных параметров функции, — 
это тот же синтаксис, что используется для написания литералов функций.

Например, если необходимо написать функцию, вычисляющую сумму двух целых чисел, её можно было бы написать так:

```dotty
val f: (Int, Int) => Int = (a, b) => a + b
```

Этот код состоит из сигнатуры типа:

```dotty
val f: (Int, Int) => Int = (a, b) => a + b
       -----------------
```

входных параметров:

```dotty
val f: (Int, Int) => Int = (a, b) => a + b
                           ------
```

и тела функции:

```dotty
val f: (Int, Int) => Int = (a, b) => a + b
                                     -----
```

Согласованность Scala состоит в том, что тип функции:

```dotty
val f: (Int, Int) => Int = (a, b) => a + b
       -----------------
```

совпадает с сигнатурой типа, используемого для определения входного параметра функции:

```dotty
def executeAndPrint(f: (Int, Int) => Int, ...
                       -----------------
```

По мере освоения этого синтаксиса, становится привычным его использование для определения параметров функций, 
анонимных функций и функциональных переменных, 
а также становится легче читать Scaladoc для функций высшего порядка.


---

**Ссылки:**

- [Scala3 book](https://docs.scala-lang.org/scala3/book/fun-hofs.html)
- [Higher-order Functions for OO Programmers - Rock the JVM](https://www.youtube.com/watch?v=GG19t3xn0D0&list=PLmtsMNDRU0Bzj7INIrLugi3a_WClwQuiS)
